{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 基于ERNIE 4.5 Turbo VL + RAG + 知识图谱的智慧博物馆讲解助手\n",
    "\n",
    "本教程将指导您使用ERNIE 4.5 Turbo VL + RAG + 知识图谱构建一个智慧博物馆讲解助手，它可以识别展品图片，提供专业的讲解，并将展品置于更广泛的历史文化背景中。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1 项目概述\n",
    "\n",
    "我们将构建一个基于ERNIE 4.5 Turbo VL + RAG + 知识图谱构建一个智慧博物馆讲解助手，它具有以下核心功能：\n",
    "\n",
    "* 文物图像识别：上传文物图片，系统自动识别文物类型\n",
    "\n",
    "* 专业讲解生成：结合RAG和知识图谱提供深入、准确的文物讲解\n",
    "\n",
    "* 知识图谱展示：可视化文物与历史人物、时代、收藏地等实体的关系\n",
    "\n",
    "* 历史时间线：将文物放在中国历史朝代的时间线上展示\n",
    "\n",
    "* 智能问答：回答用户关于文物的各类问题\n",
    "\n",
    "[**应用链接：**](https://aistudio.baidu.com/app/highcode/82110/app) https://aistudio.baidu.com/app/highcode/82110/app\n",
    "\n",
    "![](https://ai-studio-static-online.cdn.bcebos.com/9e75641c97c2411fa9a96870f998105cd00fc49111634b3590cf7d3d607c7f57)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2 环境配置\n",
    "1. 首先，我们需要安装必要的依赖包。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 初始化ERNIE客户端，用于图像识别和文本生成。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import json\n",
    "import numpy as np\n",
    "import jieba\n",
    "import networkx as nx\n",
    "import matplotlib.pyplot as plt\n",
    "import faiss\n",
    "import base64\n",
    "import hashlib\n",
    "from datetime import datetime\n",
    "import pandas as pd\n",
    "from openai import OpenAI\n",
    "import plotly.graph_objects as go\n",
    "import plotly.express as px\n",
    "\n",
    "MUSEUM_DOCS_PATH = \"./data/museum_docs\"\n",
    "VECTOR_DB_PATH = \"./data/vector_db\"\n",
    "KNOWLEDGE_GRAPH_PATH = \"./data/museum_kg.json\"\n",
    "VECTOR_INDEX_FILE = os.path.join(VECTOR_DB_PATH, \"faiss.index\")\n",
    "DOCS_INFO_FILE = os.path.join(VECTOR_DB_PATH, \"documents.json\")\n",
    "UPLOADED_IMAGES_DIR = \"./uploaded_images\"\n",
    "\n",
    "for dir_path in [MUSEUM_DOCS_PATH, VECTOR_DB_PATH, UPLOADED_IMAGES_DIR]:\n",
    "    os.makedirs(dir_path, exist_ok=True)\n",
    "\n",
    "def init_ernie_client(api_key=None):\n",
    "    \"\"\"初始化ERNIE模型API客户端\"\"\"\n",
    "    if not api_key:\n",
    "        api_key = os.environ.get(\"AI_STUDIO_API_KEY\")\n",
    "    \n",
    "    if not api_key:\n",
    "        print(\"请设置API密钥\")\n",
    "        return None\n",
    "    \n",
    "    try:\n",
    "        client = OpenAI(\n",
    "            api_key=api_key,\n",
    "            base_url=\"https://aistudio.baidu.com/llm/lmapi/v3\",\n",
    "        )\n",
    "        return client\n",
    "    except Exception as e:\n",
    "        print(f\"初始化ERNIE客户端失败: {str(e)}\")\n",
    "        return None\n",
    "\n",
    "api_key = \"*******************************\"  # 替换为您的实际API密钥\n",
    "client = init_ernie_client(api_key)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 加载词嵌入模型，用于文本向量化。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "初始化随机词嵌入模型\n",
      "随机词嵌入模型加载成功\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import jieba\n",
    "\n",
    "def load_embedding_model():\n",
    "    \"\"\"加载词嵌入模型\"\"\"\n",
    "    try:\n",
    "        # 使用随机初始化向量替代百度模型\n",
    "        class RandomEmbedding:\n",
    "            def __init__(self, dim=300):\n",
    "                self.dim = dim\n",
    "                self.word_dict = {}\n",
    "                print(\"初始化随机词嵌入模型\")\n",
    "                \n",
    "            def search(self, word):\n",
    "                if word not in self.word_dict:\n",
    "                    # 为新词创建随机向量\n",
    "                    self.word_dict[word] = np.random.randn(self.dim)\n",
    "                return self.word_dict[word]\n",
    "        \n",
    "        model = RandomEmbedding()\n",
    "        print(\"随机词嵌入模型加载成功\")\n",
    "        return model\n",
    "    except Exception as e:\n",
    "        print(f\"加载词嵌入模型失败: {str(e)}\")\n",
    "        return None\n",
    "\n",
    "embedding_model = load_embedding_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3 构建知识库和向量数据库\n",
    "\n",
    "1. 我们需要为文物创建一个知识库，并构建向量数据库用于语义搜索。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "已创建 3 个示例文档\n"
     ]
    }
   ],
   "source": [
    "def create_sample_documents():\n",
    "    \"\"\"为演示创建几个示例文物文档\"\"\"\n",
    "    samples = {\n",
    "        \"青铜器.txt\": \"\"\"青铜器是中国古代文明的重要标志之一，始于夏朝（约公元前2070年-前1600年），兴盛于商周时期。它们由铜、锡和铅的合金铸造而成，具有卓越的硬度和光泽。青铜器种类繁多，主要包括礼器（如鼎、簋、尊等）、乐器（如编钟）、兵器和生活用具等。\n",
    "\n",
    "青铜器的铸造工艺高度发达，主要采用范铸法，即先制作陶范，然后将熔化的青铜液浇入范模中冷却成型。商周青铜器的突出特点是表面装饰丰富，常见的纹饰有兽面纹、夔龙纹、蟠螭纹和几何纹等，充满神秘的宗教色彩。\n",
    "\n",
    "在中国古代社会，青铜器不仅是实用工具，更重要的是宗教礼器和权力象征。贵族使用的青铜礼器数量和级别严格按照等级制度规定，体现了\"礼制\"文化。如何保存青铜器也是一门重要的文物保护技术，涉及防锈、去锈和环境控制等多方面。\n",
    "\n",
    "代表性的青铜器文物包括\"司母戊鼎\"（商代，重832.84公斤，中国出土最大的青铜器）、\"四羊方尊\"（商代，造型独特，雕刻精美）和\"曾侯乙编钟\"（战国早期，音律精准，音色优美）等。这些文物现主要收藏于中国国家博物馆、故宫博物院等重要博物馆中。\"\"\",\n",
    "        \n",
    "        \"兵马俑.txt\": \"\"\"兵马俑，又称秦始皇兵马俑，位于中国陕西省西安市临潼区，是中国古代辉煌文明的杰出代表，被誉为\"世界第八大奇迹\"。这是秦始皇（前259年-前210年）为其陵墓所建造的大型随葬坑，目的是在阴间保护秦始皇。\n",
    "\n",
    "兵马俑于1974年被当地农民在打井时意外发现，震惊了世界。发掘表明，兵马俑坑占地面积约56,000平方米，共有三个俑坑。目前已出土陶俑8,000余个，每个俑高约180厘米，重约200公斤，包括步兵、骑兵、车兵、将军等不同兵种，形态各异，表情生动，被誉为\"世界上最伟大的考古发现之一\"。\n",
    "\n",
    "兵马俑的制作工艺极为精湛。先用模具制作身体各部位，然后组装成完整人像，最后再进行细部雕琢和彩绘。每个兵俑面部特征各不相同，据考证是以真人为模特创作的。兵马俑原本都有鲜艳的彩绘，但出土后由于空气氧化，颜色迅速褪去。\n",
    "\n",
    "兵马俑的发现为研究秦代历史、军事、艺术和手工业提供了珍贵资料，展示了秦朝强大的军事实力和高度发达的制陶工艺，也反映了秦始皇的雄心壮志和对生死的态度。1987年，秦始皇陵及兵马俑被联合国教科文组织列入《世界文化遗产名录》。每年，数以百万计的游客前往参观这一人类文明的瑰宝。\"\"\",\n",
    "        \n",
    "        \"敦煌壁画.txt\": \"\"\"敦煌壁画，是指中国甘肃省敦煌莫高窟内的大量佛教壁画，创作于十六国至元代（4世纪-14世纪），跨越近千年历史。莫高窟现存492个洞窟，壁画总面积达45,000多平方米，是世界上规模最大、内容最丰富的佛教艺术宝库。\n",
    "\n",
    "敦煌壁画的内容主要以佛教故事、佛陀生平、经变画、供养人像等为主，同时也包含大量世俗生活、民俗风情、历史事件等场景，为研究古代社会、文化、艺术、宗教提供了珍贵资料。壁画艺术风格随时代变化明显，从北魏的线条简朴、形体修长，到隋唐的丰满圆润、色彩艳丽，再到宋元的世俗化倾向，反映了中国传统绘画的演变。\n",
    "\n",
    "敦煌壁画的绘制技法精湛。首先在洞窟壁面涂抹泥层，待干后涂白灰底，然后打稿、勾线、罩色、描金。色彩鲜艳夺目，以矿物颜料为主，包括朱砂、石青、石绿等，经过千年仍保持鲜艳。构图饱满，层次分明，人物形象生动传神，衣纹飘逸流畅。\n",
    "\n",
    "1900年，道士王圆箓在莫高窟第17窟发现大量古代文献，即\"敦煌文书\"，此后敦煌艺术逐渐为世界所知。莫高窟于1961年被列为中国首批全国重点文物保护单位，1987年被联合国教科文组织列入《世界文化遗产名录》。近年来，数字化保护工作使这些珍贵的文化遗产得以更好地保存和传播。\"\"\"\n",
    "    }\n",
    "    \n",
    "    for filename, content in samples.items():\n",
    "        file_path = os.path.join(MUSEUM_DOCS_PATH, filename)\n",
    "        with open(file_path, 'w', encoding='utf-8') as f:\n",
    "            f.write(content)\n",
    "    \n",
    "    print(f\"已创建 {len(samples)} 个示例文档\")\n",
    "\n",
    "create_sample_documents()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Building prefix dict from the default dictionary ...\n",
      "Dumping model to file cache C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\jieba.cache\n",
      "Loading model cost 0.721 seconds.\n",
      "Prefix dict has been built successfully.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "文本嵌入过程出错: unhashable type: 'list'\n",
      "文本嵌入过程出错: unhashable type: 'list'\n",
      "文本嵌入过程出错: unhashable type: 'list'\n",
      "向量数据库构建完成，包含 3 个文档\n"
     ]
    }
   ],
   "source": [
    "def embed_text(text, embedding_model):\n",
    "    \"\"\"将文本转换为向量嵌入\"\"\"\n",
    "    if embedding_model is None:\n",
    "        return np.zeros(300)\n",
    "    \n",
    "    try:\n",
    "        words = list(jieba.cut(text))\n",
    "        word_embeddings = embedding_model.search(words)\n",
    "        \n",
    "        if len(word_embeddings) > 0:\n",
    "            return np.mean(word_embeddings, axis=0)\n",
    "        else:\n",
    "            return np.zeros(300)\n",
    "    except Exception as e:\n",
    "        print(f\"文本嵌入过程出错: {str(e)}\")\n",
    "        return np.zeros(300)\n",
    "\n",
    "def build_vector_database():\n",
    "    \"\"\"构建文物知识的向量数据库\"\"\"\n",
    "    documents = []\n",
    "    \n",
    "    for filename in os.listdir(MUSEUM_DOCS_PATH):\n",
    "        if filename.endswith('.txt'):\n",
    "            file_path = os.path.join(MUSEUM_DOCS_PATH, filename)\n",
    "            with open(file_path, 'r', encoding='utf-8') as f:\n",
    "                content = f.read()\n",
    "                \n",
    "                doc = {\n",
    "                    \"content\": content,\n",
    "                    \"metadata\": {\n",
    "                        \"source\": file_path,\n",
    "                        \"filename\": filename\n",
    "                    }\n",
    "                }\n",
    "                documents.append(doc)\n",
    "    \n",
    "    if not documents:\n",
    "        print(\"没有找到文档，无法构建向量数据库\")\n",
    "        return None, []\n",
    "    \n",
    "    embeddings = []\n",
    "    for doc in documents:\n",
    "        vector = embed_text(doc[\"content\"], embedding_model)\n",
    "        if vector is not None and vector.size > 0:\n",
    "            embeddings.append(vector)\n",
    "    \n",
    "    if not embeddings:\n",
    "        print(\"无法生成文档嵌入，无法构建向量数据库\")\n",
    "        return None, documents\n",
    "    \n",
    "    embeddings_np = np.array(embeddings).astype('float32')\n",
    "    \n",
    "    if len(embeddings_np.shape) < 2:\n",
    "        print(f\"嵌入数组形状不正确: {embeddings_np.shape}\")\n",
    "        if embeddings_np.size > 0:\n",
    "            embeddings_np = embeddings_np.reshape(1, -1)\n",
    "            print(f\"已重塑为: {embeddings_np.shape}\")\n",
    "        else:\n",
    "            return None, documents\n",
    "    \n",
    "    dimension = embeddings_np.shape[1] \n",
    "    index = faiss.IndexFlatL2(dimension)  \n",
    "    index.add(embeddings_np)\n",
    "    \n",
    "    faiss.write_index(index, VECTOR_INDEX_FILE)\n",
    "    with open(DOCS_INFO_FILE, 'w', encoding='utf-8') as f:\n",
    "        json.dump(documents, f, ensure_ascii=False, indent=2)\n",
    "    \n",
    "    print(f\"向量数据库构建完成，包含 {len(documents)} 个文档\")\n",
    "    return index, documents\n",
    "\n",
    "def load_vector_db():\n",
    "    \"\"\"加载向量数据库\"\"\"\n",
    "    if os.path.exists(VECTOR_INDEX_FILE) and os.path.exists(DOCS_INFO_FILE):\n",
    "        try:\n",
    "            index = faiss.read_index(VECTOR_INDEX_FILE)\n",
    "            with open(DOCS_INFO_FILE, 'r', encoding='utf-8') as f:\n",
    "                documents = json.load(f)\n",
    "            print(f\"向量数据库加载成功，包含 {len(documents)} 个文档\")\n",
    "            return index, documents\n",
    "        except Exception as e:\n",
    "            print(f\"加载向量数据库失败: {str(e)}\")\n",
    "    return None, None\n",
    "\n",
    "def search_similar_docs(query_embedding, index, documents, top_k=5):\n",
    "    \"\"\"搜索相似文档\"\"\"\n",
    "    if index is None or documents is None:\n",
    "        return []\n",
    "    \n",
    "    try:\n",
    "        query_embedding = np.array([query_embedding]).astype('float32')\n",
    "        \n",
    "        distances, indices = index.search(query_embedding, top_k)\n",
    "        \n",
    "        results = []\n",
    "        for i, idx in enumerate(indices[0]):\n",
    "            if idx != -1 and idx < len(documents):\n",
    "                similarity = 1.0 / (1.0 + float(distances[0][i]))\n",
    "                \n",
    "                results.append({\n",
    "                    \"content\": documents[idx][\"content\"],\n",
    "                    \"metadata\": documents[idx][\"metadata\"],\n",
    "                    \"score\": similarity\n",
    "                })\n",
    "        \n",
    "        return results\n",
    "    except Exception as e:\n",
    "        print(f\"向量搜索过程出错: {str(e)}\")\n",
    "        return []\n",
    "\n",
    "if not os.path.exists(VECTOR_INDEX_FILE) or not os.path.exists(DOCS_INFO_FILE):\n",
    "    index, documents = build_vector_database()\n",
    "else:\n",
    "    index, documents = load_vector_db()\n",
    "    print(f\"向量数据库加载成功，包含 {len(documents)} 个文档\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 创建知识图谱\n",
    "接下来，我们将创建一个知识图谱，连接文物与相关的历史人物、时代、收藏地等实体。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "知识图谱创建完成，包含 32 个节点和 35 个关系\n",
      "青铜器相关的关系数量: 4\n",
      "- 商朝 (era): created_in\n",
      "- 周朝 (era): created_in\n",
      "- 中国国家博物馆 (collection): collected_by\n"
     ]
    }
   ],
   "source": [
    "def create_knowledge_graph():\n",
    "    \"\"\"创建文物知识图谱\"\"\"\n",
    "    # 创建空的知识图谱\n",
    "    kg_data = {\n",
    "        \"nodes\": [],\n",
    "        \"edges\": []\n",
    "    }\n",
    "    \n",
    "    # 文物节点\n",
    "    artifacts = [\n",
    "        {\"id\": \"a1\", \"name\": \"青铜器\", \"type\": \"artifact\", \"properties\": {\"material\": \"青铜\"}},\n",
    "        {\"id\": \"a2\", \"name\": \"兵马俑\", \"type\": \"artifact\", \"properties\": {\"material\": \"陶土\"}},\n",
    "        {\"id\": \"a3\", \"name\": \"敦煌壁画\", \"type\": \"artifact\", \"properties\": {\"material\": \"颜料\"}},\n",
    "        {\"id\": \"a4\", \"name\": \"唐三彩\", \"type\": \"artifact\", \"properties\": {\"material\": \"陶土\"}},\n",
    "        {\"id\": \"a5\", \"name\": \"越王勾践剑\", \"type\": \"artifact\", \"properties\": {\"material\": \"青铜\"}},\n",
    "        {\"id\": \"a6\", \"name\": \"甲骨文\", \"type\": \"artifact\", \"properties\": {\"material\": \"龟甲兽骨\"}},\n",
    "        {\"id\": \"a7\", \"name\": \"四羊方尊\", \"type\": \"artifact\", \"properties\": {\"material\": \"青铜\"}},\n",
    "        {\"id\": \"a8\", \"name\": \"汉长信宫灯\", \"type\": \"artifact\", \"properties\": {\"material\": \"青铜\"}},\n",
    "        {\"id\": \"a9\", \"name\": \"曾侯乙编钟\", \"type\": \"artifact\", \"properties\": {\"material\": \"青铜\"}},\n",
    "        {\"id\": \"a10\", \"name\": \"清明上河图\", \"type\": \"artifact\", \"properties\": {\"material\": \"绢本\"}}\n",
    "    ]\n",
    "    \n",
    "    # 朝代/时期节点\n",
    "    eras = [\n",
    "        {\"id\": \"e1\", \"name\": \"商朝\", \"type\": \"era\"},\n",
    "        {\"id\": \"e2\", \"name\": \"周朝\", \"type\": \"era\"},\n",
    "        {\"id\": \"e3\", \"name\": \"秦朝\", \"type\": \"era\"},\n",
    "        {\"id\": \"e4\", \"name\": \"汉朝\", \"type\": \"era\"},\n",
    "        {\"id\": \"e5\", \"name\": \"唐朝\", \"type\": \"era\"},\n",
    "        {\"id\": \"e6\", \"name\": \"宋朝\", \"type\": \"era\"}\n",
    "    ]\n",
    "    \n",
    "    # 人物节点\n",
    "    persons = [\n",
    "        {\"id\": \"p1\", \"name\": \"秦始皇\", \"type\": \"person\"},\n",
    "        {\"id\": \"p2\", \"name\": \"越王勾践\", \"type\": \"person\"},\n",
    "        {\"id\": \"p3\", \"name\": \"曾侯乙\", \"type\": \"person\"},\n",
    "        {\"id\": \"p4\", \"name\": \"张择端\", \"type\": \"person\"}\n",
    "    ]\n",
    "    \n",
    "    # 收藏/出土地点节点\n",
    "    collections = [\n",
    "        {\"id\": \"c1\", \"name\": \"中国国家博物馆\", \"type\": \"collection\"},\n",
    "        {\"id\": \"c2\", \"name\": \"秦始皇兵马俑博物馆\", \"type\": \"collection\"},\n",
    "        {\"id\": \"c3\", \"name\": \"敦煌莫高窟\", \"type\": \"collection\"},\n",
    "        {\"id\": \"c4\", \"name\": \"湖北省博物馆\", \"type\": \"collection\"},\n",
    "        {\"id\": \"c5\", \"name\": \"故宫博物院\", \"type\": \"collection\"}\n",
    "    ]\n",
    "    \n",
    "    # 类别节点\n",
    "    categories = [\n",
    "        {\"id\": \"cat1\", \"name\": \"青铜礼器\", \"type\": \"category\"},\n",
    "        {\"id\": \"cat2\", \"name\": \"陶俑\", \"type\": \"category\"},\n",
    "        {\"id\": \"cat3\", \"name\": \"壁画\", \"type\": \"category\"},\n",
    "        {\"id\": \"cat4\", \"name\": \"彩陶\", \"type\": \"category\"},\n",
    "        {\"id\": \"cat5\", \"name\": \"兵器\", \"type\": \"category\"},\n",
    "        {\"id\": \"cat6\", \"name\": \"甲骨\", \"type\": \"category\"},\n",
    "        {\"id\": \"cat7\", \"name\": \"绘画\", \"type\": \"category\"}\n",
    "    ]\n",
    "    \n",
    "    # 添加所有节点\n",
    "    kg_data[\"nodes\"] = artifacts + eras + persons + collections + categories\n",
    "    \n",
    "    # 添加边/关系\n",
    "    edges = [\n",
    "        # 文物与时代的关系\n",
    "        {\"source\": \"a1\", \"target\": \"e1\", \"type\": \"created_in\"},\n",
    "        {\"source\": \"a1\", \"target\": \"e2\", \"type\": \"created_in\"},\n",
    "        {\"source\": \"a2\", \"target\": \"e3\", \"type\": \"created_in\"},\n",
    "        {\"source\": \"a3\", \"target\": \"e5\", \"type\": \"created_in\"},\n",
    "        {\"source\": \"a4\", \"target\": \"e5\", \"type\": \"created_in\"},\n",
    "        {\"source\": \"a5\", \"target\": \"e2\", \"type\": \"created_in\"},\n",
    "        {\"source\": \"a6\", \"target\": \"e1\", \"type\": \"created_in\"},\n",
    "        {\"source\": \"a7\", \"target\": \"e1\", \"type\": \"created_in\"},\n",
    "        {\"source\": \"a8\", \"target\": \"e4\", \"type\": \"created_in\"},\n",
    "        {\"source\": \"a9\", \"target\": \"e2\", \"type\": \"created_in\"},\n",
    "        {\"source\": \"a10\", \"target\": \"e6\", \"type\": \"created_in\"},\n",
    "        \n",
    "        # 文物与人物的关系\n",
    "        {\"source\": \"a2\", \"target\": \"p1\", \"type\": \"associated_with\"},\n",
    "        {\"source\": \"a5\", \"target\": \"p2\", \"type\": \"owned_by\"},\n",
    "        {\"source\": \"a9\", \"target\": \"p3\", \"type\": \"owned_by\"},\n",
    "        {\"source\": \"a10\", \"target\": \"p4\", \"type\": \"created_by\"},\n",
    "        \n",
    "        # 文物与收藏地/出土地的关系\n",
    "        {\"source\": \"a1\", \"target\": \"c1\", \"type\": \"collected_by\"},\n",
    "        {\"source\": \"a2\", \"target\": \"c2\", \"type\": \"found_in\"},\n",
    "        {\"source\": \"a3\", \"target\": \"c3\", \"type\": \"housed_in\"},\n",
    "        {\"source\": \"a4\", \"target\": \"c1\", \"type\": \"collected_by\"},\n",
    "        {\"source\": \"a5\", \"target\": \"c5\", \"type\": \"collected_by\"},\n",
    "        {\"source\": \"a6\", \"target\": \"c1\", \"type\": \"collected_by\"},\n",
    "        {\"source\": \"a7\", \"target\": \"c1\", \"type\": \"collected_by\"},\n",
    "        {\"source\": \"a8\", \"target\": \"c5\", \"type\": \"collected_by\"},\n",
    "        {\"source\": \"a9\", \"target\": \"c4\", \"type\": \"collected_by\"},\n",
    "        {\"source\": \"a10\", \"target\": \"c5\", \"type\": \"collected_by\"},\n",
    "        \n",
    "        # 文物与类别的关系\n",
    "        {\"source\": \"a1\", \"target\": \"cat1\", \"type\": \"belongs_to\"},\n",
    "        {\"source\": \"a2\", \"target\": \"cat2\", \"type\": \"belongs_to\"},\n",
    "        {\"source\": \"a3\", \"target\": \"cat3\", \"type\": \"belongs_to\"},\n",
    "        {\"source\": \"a4\", \"target\": \"cat4\", \"type\": \"belongs_to\"},\n",
    "        {\"source\": \"a5\", \"target\": \"cat5\", \"type\": \"belongs_to\"},\n",
    "        {\"source\": \"a6\", \"target\": \"cat6\", \"type\": \"belongs_to\"},\n",
    "        {\"source\": \"a7\", \"target\": \"cat1\", \"type\": \"belongs_to\"},\n",
    "        {\"source\": \"a8\", \"target\": \"cat1\", \"type\": \"belongs_to\"},\n",
    "        {\"source\": \"a9\", \"target\": \"cat1\", \"type\": \"belongs_to\"},\n",
    "        {\"source\": \"a10\", \"target\": \"cat7\", \"type\": \"belongs_to\"}\n",
    "    ]\n",
    "    \n",
    "    kg_data[\"edges\"] = edges\n",
    "    \n",
    "    with open(KNOWLEDGE_GRAPH_PATH, 'w', encoding='utf-8') as f:\n",
    "        json.dump(kg_data, f, ensure_ascii=False, indent=2)\n",
    "    \n",
    "    print(f\"知识图谱创建完成，包含 {len(kg_data['nodes'])} 个节点和 {len(kg_data['edges'])} 个关系\")\n",
    "    return kg_data\n",
    "\n",
    "def load_knowledge_graph():\n",
    "    \"\"\"加载知识图谱\"\"\"\n",
    "    if os.path.exists(KNOWLEDGE_GRAPH_PATH):\n",
    "        try:\n",
    "            with open(KNOWLEDGE_GRAPH_PATH, 'r', encoding='utf-8') as f:\n",
    "                kg_data = json.load(f)\n",
    "                print(f\"知识图谱加载成功，包含 {len(kg_data['nodes'])} 个节点和 {len(kg_data['edges'])} 个关系\")\n",
    "                return kg_data\n",
    "        except Exception as e:\n",
    "            print(f\"加载知识图谱失败: {str(e)}\")\n",
    "    return None\n",
    "\n",
    "def get_related_kg_info(artifact_name, kg_data):\n",
    "    \"\"\"获取与文物相关的知识图谱信息\"\"\"\n",
    "    if not kg_data:\n",
    "        return []\n",
    "    \n",
    "    try:\n",
    "        artifact_id = None\n",
    "        for node in kg_data['nodes']:\n",
    "            if node['name'] == artifact_name and node['type'] == 'artifact':\n",
    "                artifact_id = node['id']\n",
    "                break\n",
    "        \n",
    "        if not artifact_id:\n",
    "            return []\n",
    "        \n",
    "        relations = []\n",
    "        \n",
    "        for edge in kg_data['edges']:\n",
    "            if edge['source'] == artifact_id:\n",
    "                target_node = next((node for node in kg_data['nodes'] if node['id'] == edge['target']), None)\n",
    "                if target_node:\n",
    "                    relations.append({\n",
    "                        \"relation\": edge['type'],\n",
    "                        \"entity\": target_node['name'],\n",
    "                        \"type\": target_node['type'],\n",
    "                        \"details\": edge.get('properties', {})\n",
    "                    })\n",
    "            elif edge['target'] == artifact_id:\n",
    "                source_node = next((node for node in kg_data['nodes'] if node['id'] == edge['source']), None)\n",
    "                if source_node:\n",
    "                    relations.append({\n",
    "                        \"relation\": edge['type'],\n",
    "                        \"entity\": source_node['name'],\n",
    "                        \"type\": source_node['type'],\n",
    "                        \"details\": edge.get('properties', {})\n",
    "                    })\n",
    "        \n",
    "        return relations\n",
    "    except Exception as e:\n",
    "        print(f\"获取知识图谱信息过程出错: {str(e)}\")\n",
    "        return []\n",
    "\n",
    "def format_kg_info(relations):\n",
    "    \"\"\"格式化知识图谱信息\"\"\"\n",
    "    if not relations:\n",
    "        return \"\"\n",
    "    \n",
    "    try:\n",
    "        formatted_info = \"📚 知识图谱补充信息：\\n\\n\"\n",
    "        \n",
    "        # 按实体类型分组关系\n",
    "        type_groups = {}\n",
    "        for rel in relations:\n",
    "            entity_type = rel['type']\n",
    "            if entity_type not in type_groups:\n",
    "                type_groups[entity_type] = []\n",
    "            type_groups[entity_type].append(rel)\n",
    "        \n",
    "        # 按优先级顺序处理实体类型\n",
    "        priority_types = ['era', 'person', 'collection', 'category']\n",
    "        for entity_type in priority_types:\n",
    "            if entity_type in type_groups:\n",
    "                relations_of_type = type_groups[entity_type]\n",
    "                \n",
    "                type_name = {\n",
    "                    'artifact': '相关文物',\n",
    "                    'person': '相关人物',\n",
    "                    'era': '历史时期',\n",
    "                    'collection': '收藏/出土地点',\n",
    "                    'category': '文物类别'\n",
    "                }.get(entity_type, entity_type)\n",
    "                \n",
    "                formatted_info += f\"- **{type_name}**: \"\n",
    "                \n",
    "                entity_relations = []\n",
    "                for r in relations_of_type:\n",
    "                    relation_name = {\n",
    "                        \"created_in\": \"创作/制作于\",\n",
    "                        \"belongs_to\": \"属于\",\n",
    "                        \"houses\": \"收藏于\",\n",
    "                        \"created_by\": \"创作者\",\n",
    "                        \"associated_with\": \"相关于\",\n",
    "                        \"found_in\": \"发现于\",\n",
    "                        \"owned_by\": \"拥有者\",\n",
    "                        \"collected_by\": \"收藏于\",\n",
    "                        \"flourished_in\": \"繁荣于\"\n",
    "                    }.get(r['relation'], r['relation'])\n",
    "                    \n",
    "                    entity_relations.append(f\"{r['entity']}（{relation_name}）\")\n",
    "                \n",
    "                formatted_info += \"、\".join(entity_relations) + \"\\n\"\n",
    "        \n",
    "        for entity_type, relations_of_type in type_groups.items():\n",
    "            if entity_type not in priority_types:\n",
    "                type_name = {\n",
    "                    'artifact': '相关文物',\n",
    "                    'person': '相关人物',\n",
    "                    'era': '历史时期',\n",
    "                    'collection': '收藏/出土地点',\n",
    "                    'category': '文物类别'\n",
    "                }.get(entity_type, entity_type)\n",
    "                \n",
    "                formatted_info += f\"- **{type_name}**: \"\n",
    "                \n",
    "                entity_relations = []\n",
    "                for r in relations_of_type:\n",
    "                    relation_name = {\n",
    "                        \"created_in\": \"创作/制作于\",\n",
    "                        \"belongs_to\": \"属于\",\n",
    "                        \"houses\": \"收藏于\",\n",
    "                        \"created_by\": \"创作者\",\n",
    "                        \"associated_with\": \"相关于\",\n",
    "                        \"found_in\": \"发现于\",\n",
    "                        \"owned_by\": \"拥有者\",\n",
    "                        \"collected_by\": \"收藏于\",\n",
    "                        \"flourished_in\": \"繁荣于\"\n",
    "                    }.get(r['relation'], r['relation'])\n",
    "                    \n",
    "                    entity_relations.append(f\"{r['entity']}（{relation_name}）\")\n",
    "                \n",
    "                formatted_info += \"、\".join(entity_relations) + \"\\n\"\n",
    "        \n",
    "        return formatted_info\n",
    "    except Exception as e:\n",
    "        print(f\"格式化知识图谱信息过程出错: {str(e)}\")\n",
    "        return \"\"\n",
    "\n",
    "kg_data = create_knowledge_graph()\n",
    "relations = get_related_kg_info(\"青铜器\", kg_data)\n",
    "print(f\"青铜器相关的关系数量: {len(relations)}\")\n",
    "for rel in relations[:3]:  \n",
    "    print(f\"- {rel['entity']} ({rel['type']}): {rel['relation']}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. 文物图像识别\n",
    "\n",
    "文心一言大模型全新版本，图片理解、创作、翻译、代码等能力显著提升，首次支持32K上下文长度，首Token时延显著降低。\n",
    "\n",
    "| 模型名称 | model 参数值 | 上下文长度(token) | 最大输入(token) | 最大输出(token) |\n",
    "| :--------: | :--------: | :--------: | :--------: | :--------: |\n",
    "| ERNIE 4.5 Turbo VL | ernie-4.5-turbo-vl-32k | 32k | 27k token 270000字符 | [2，12288] | 默认 2k |\n",
    "| ERNIE 4.5\t| ernie-4.5-8k-preview | 8k | 5k token 50000字符 | [2，2048] | 默认 2k |\n",
    "\n",
    "\n",
    "我们使用ERNIE 4.5 Turbo VL模型进行文物图像识别。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ERNIE VL文物识别过程出错: [Errno 2] No such file or directory: 'uploaded_images/tangsaican.jpeg'\n",
      "识别结果: 未知文物, 置信度: 0.00\n"
     ]
    }
   ],
   "source": [
    "import base64\n",
    "import hashlib\n",
    "from datetime import datetime\n",
    "import os\n",
    "\n",
    "UPLOADED_IMAGES_DIR = \"./uploaded_images\"\n",
    "os.makedirs(UPLOADED_IMAGES_DIR, exist_ok=True)\n",
    "\n",
    "def save_uploaded_image(file_content, filename=None):\n",
    "    try:\n",
    "        if filename is None:\n",
    "            timestamp = datetime.now().strftime(\"%Y%m%d%H%M%S\")\n",
    "            unique_id = hashlib.md5(timestamp.encode()).hexdigest()\n",
    "            filename = f\"{unique_id}.jpg\"\n",
    "        \n",
    "        filepath = os.path.join(UPLOADED_IMAGES_DIR, filename)\n",
    "        \n",
    "        with open(filepath, \"wb\") as f:\n",
    "            f.write(file_content)\n",
    "        \n",
    "        return filepath\n",
    "    except Exception as e:\n",
    "        print(f\"保存图像过程出错: {str(e)}\")\n",
    "        return None\n",
    "\n",
    "def classify_artifact_with_ernie(client, image_path, kg_data):\n",
    "    \"\"\"使用ERNIE VL模型进行文物图像识别\"\"\"\n",
    "    if client is None or kg_data is None:\n",
    "        return \"未知文物\", 0.0\n",
    "    \n",
    "    try:\n",
    "        with open(image_path, \"rb\") as image_file:\n",
    "            base64_image = base64.b64encode(image_file.read()).decode('utf-8')\n",
    "        \n",
    "        prompt = \"\"\"你是一位专业的中国古代文物识别专家。请分析这张图片中的文物，并告诉我：\n",
    "1. 这是什么文物？（如：青铜器、兵马俑、敦煌壁画、唐三彩等）\n",
    "2. 你的识别置信度（0.0-1.0之间的数值）\n",
    "\n",
    "只需简单回答：文物名称，置信度。例如：\n",
    "青铜器，0.95\n",
    "\"\"\"\n",
    "        \n",
    "        messages = [\n",
    "            {\n",
    "                'role': 'user', \n",
    "                'content': [\n",
    "                    {\"type\": \"text\", \"text\": prompt},\n",
    "                    {\n",
    "                        \"type\": \"image_url\",\n",
    "                        \"image_url\": {\"url\": f\"data:image/jpeg;base64,{base64_image}\"}\n",
    "                    }\n",
    "                ]\n",
    "            }\n",
    "        ]\n",
    "        \n",
    "        completion = client.chat.completions.create(\n",
    "            model=\"ernie-4.5-turbo-vl-32k\",\n",
    "            messages=messages,\n",
    "            temperature=0.1,\n",
    "        )\n",
    "        \n",
    "        response = completion.choices[0].message.content\n",
    "        \n",
    "        artifact_name = \"未知文物\"\n",
    "        confidence = 0.5\n",
    "        \n",
    "        try:\n",
    "            response = response.strip().replace('，', ',').replace('：', ':')\n",
    "            \n",
    "            if ',' in response:\n",
    "                parts = response.split(',', 1)\n",
    "                artifact_name = parts[0].strip()\n",
    "                import re\n",
    "                conf_matches = re.findall(r'0\\.\\d+', parts[1])\n",
    "                if conf_matches:\n",
    "                    confidence = float(conf_matches[0])\n",
    "            \n",
    "            if artifact_name != \"未知文物\" and confidence == 0.5:\n",
    "                confidence = 0.8\n",
    "                \n",
    "            artifact_names = [node['name'] for node in kg_data['nodes'] if node['type'] == 'artifact']\n",
    "            \n",
    "            exact_match = False\n",
    "            for known_artifact in artifact_names:\n",
    "                if artifact_name == known_artifact:\n",
    "                    exact_match = True\n",
    "                    break\n",
    "            \n",
    "            if not exact_match:\n",
    "                best_match = None\n",
    "                best_score = 0\n",
    "                \n",
    "                for known_artifact in artifact_names:\n",
    "                    match_count = 0\n",
    "                    for char in artifact_name:\n",
    "                        if char in known_artifact:\n",
    "                            match_count += 1\n",
    "                    \n",
    "                    score = match_count / max(len(artifact_name), len(known_artifact))\n",
    "                    \n",
    "                    if score > best_score and score > 0.5: \n",
    "                        best_score = score\n",
    "                        best_match = known_artifact\n",
    "                \n",
    "                if best_match:\n",
    "                    artifact_name = best_match\n",
    "                    confidence = max(0.6, confidence * 0.9)\n",
    "        \n",
    "        except Exception as e:\n",
    "            print(f\"解析ERNIE响应时出错: {str(e)}\")\n",
    "            if \"青铜\" in response:\n",
    "                artifact_name = \"青铜器\"\n",
    "                confidence = 0.7\n",
    "            elif \"兵马俑\" in response:\n",
    "                artifact_name = \"兵马俑\"\n",
    "                confidence = 0.7\n",
    "            elif \"敦煌\" in response:\n",
    "                artifact_name = \"敦煌壁画\"\n",
    "                confidence = 0.7\n",
    "        \n",
    "        return artifact_name, confidence\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"ERNIE VL文物识别过程出错: {str(e)}\")\n",
    "        return \"未知文物\", 0.0\n",
    "\n",
    "test_image_path = \"uploaded_images/tangsaican.jpeg\"\n",
    "artifact_name, confidence = classify_artifact_with_ernie(client, test_image_path, kg_data)\n",
    "print(f\"识别结果: {artifact_name}, 置信度: {confidence:.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5 基于RAG的文物问答系统\n",
    "我们使用检索增强生成（RAG）和 ERNIE 4.5 Turbo VL 来实现文物问答系统。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "问题: 青铜器的历史背景是什么？\r\n",
      "回答: 青铜器啊，可是中国古代文明的一个重要标志呢！它的历史可以追溯到夏朝，大约是公元前2070年到前1600年，不过它真正兴盛起来，还是在商周时期。\r\n",
      "\r\n",
      "你知道吗？青铜器其实是由铜、锡和铅这几种金属混合而成的合金铸造的，这让它们既坚硬又有光泽。在古代，青铜器可不仅仅是实用工具哦，它们更是宗教礼器和权力的象征。\r\n",
      "\r\n",
      "说到种类，青铜器可真是五花八门，有礼器、乐器、兵器，还有生活用具等等。比如鼎、簋、尊这些礼器，在古代可是只有贵族才能使用的，而且他们使用的数量和级别还要严格按照等级制度来规定，可见其地位之重要。\r\n",
      "\r\n",
      "再来说说铸造工艺吧。古代人主要用范铸法来铸造青铜器，就是先做个陶范，然后把熔化的青铜液浇进去，等它冷却成型。这种方法虽然古老，但铸造出来的青铜器却是非常精美，表面还常常装饰有丰富的纹饰，比如兽面纹、夔龙纹等，充满了神秘的宗教色彩。\r\n",
      "\r\n",
      "所以呀，青铜器不仅是中国古代工艺美术的瑰宝，更是我们了解古代社会、文化、宗教等方面的重要实物资料。\r\n"
     ]
    }
   ],
   "source": [
    "def create_system_prompt(artifact_name=None):\n",
    "    \"\"\"创建系统提示词\"\"\"\n",
    "    prompt = \"\"\"你是一位专业的博物馆文物讲解员和中国古代文化专家，擅长深入浅出地解释中国古代文物和艺术品。\n",
    "    \n",
    "    请注意以下几点：\n",
    "    1. 你的回答应既有学术价值又生动有趣，适合各年龄层观众理解\n",
    "    2. 尽量使用生动形象的描述和比喻，帮助用户理解复杂的历史背景和艺术特点\n",
    "    3. 回答应条理清晰，重点突出，可适当分段\n",
    "    4. 引用具体细节和事实，避免泛泛而谈\n",
    "    5. 如果知识库没有信息，或不确定的内容，请坦率承认\n",
    "    6. 保持友好亲切的语气，像正在进行面对面讲解一样\n",
    "\n",
    "    在回答时，可考虑以下角度：\n",
    "    - 文物的历史背景和时代特征\n",
    "    - 文物的艺术价值和工艺特点\n",
    "    - 文物的文化意义和象征意义\n",
    "    - 文物的发现过程或收藏历史\n",
    "    - 相关历史人物或事件\n",
    "    \"\"\"\n",
    "    \n",
    "    if artifact_name:\n",
    "        prompt += f\"\\n\\n现在，用户正在了解《{artifact_name}》，请特别关注这件文物的相关信息。\"\n",
    "    \n",
    "    return prompt\n",
    "\n",
    "def format_kg_info(relations):\n",
    "    \"\"\"格式化知识图谱信息\"\"\"\n",
    "    if not relations:\n",
    "        return \"\"\n",
    "    \n",
    "    try:\n",
    "        formatted_info = \"📚 知识图谱补充信息：\\n\\n\"\n",
    "        \n",
    "        type_groups = {}\n",
    "        for rel in relations:\n",
    "            entity_type = rel['type']\n",
    "            if entity_type not in type_groups:\n",
    "                type_groups[entity_type] = []\n",
    "            type_groups[entity_type].append(rel)\n",
    "        \n",
    "        priority_types = ['era', 'person', 'collection', 'category']\n",
    "        for entity_type in priority_types:\n",
    "            if entity_type in type_groups:\n",
    "                relations_of_type = type_groups[entity_type]\n",
    "                \n",
    "                type_name = {\n",
    "                    'artifact': '相关文物',\n",
    "                    'person': '相关人物',\n",
    "                    'era': '历史时期',\n",
    "                    'collection': '收藏/出土地点',\n",
    "                    'category': '文物类别'\n",
    "                }.get(entity_type, entity_type)\n",
    "                \n",
    "                formatted_info += f\"- **{type_name}**: \"\n",
    "                \n",
    "                entity_relations = []\n",
    "                for r in relations_of_type:\n",
    "                    relation_name = {\n",
    "                        \"created_in\": \"创作/制作于\",\n",
    "                        \"belongs_to\": \"属于\",\n",
    "                        \"houses\": \"收藏于\",\n",
    "                        \"created_by\": \"创作者\",\n",
    "                        \"associated_with\": \"相关于\",\n",
    "                        \"found_in\": \"发现于\",\n",
    "                        \"owned_by\": \"拥有者\",\n",
    "                        \"collected_by\": \"收藏于\",\n",
    "                        \"flourished_in\": \"繁荣于\"\n",
    "                    }.get(r['relation'], r['relation'])\n",
    "                    \n",
    "                    entity_relations.append(f\"{r['entity']}（{relation_name}）\")\n",
    "                \n",
    "                formatted_info += \"、\".join(entity_relations) + \"\\n\"\n",
    "        \n",
    "        for entity_type, relations_of_type in type_groups.items():\n",
    "            if entity_type not in priority_types:\n",
    "                pass\n",
    "        \n",
    "        return formatted_info\n",
    "    except Exception as e:\n",
    "        print(f\"格式化知识图谱信息过程出错: {str(e)}\")\n",
    "        return \"\"\n",
    "\n",
    "def generate_response(client, query, context, image_path=None, artifact_name=None):\n",
    "\n",
    "    if client is None:\n",
    "        return \"ERNIE客户端未初始化，请设置正确的API密钥\"\n",
    "    \n",
    "    try:\n",
    "        system_prompt = create_system_prompt(artifact_name)\n",
    "        \n",
    "        messages = [\n",
    "            {'role': 'system', 'content': system_prompt}\n",
    "        ]\n",
    "        \n",
    "        context_prompt = f\"\"\"参考以下知识回答问题。如果知识库中没有相关信息，请基于你的专业知识回答，并明确指出这是你的补充解释。\n",
    "\n",
    "知识库内容:\n",
    "{context}\n",
    "\n",
    "用户询问: {query}\"\"\"\n",
    "        \n",
    "        if image_path:\n",
    "            try:\n",
    "                with open(image_path, \"rb\") as image_file:\n",
    "                    base64_image = base64.b64encode(image_file.read()).decode('utf-8')\n",
    "                \n",
    "                messages.append({\n",
    "                    \"role\": \"user\",\n",
    "                    \"content\": [\n",
    "                        {\"type\": \"text\", \"text\": context_prompt},\n",
    "                        {\"type\": \"image_url\", \"image_url\": {\"url\": f\"data:image/jpeg;base64,{base64_image}\"}}\n",
    "                    ]\n",
    "                })\n",
    "            except Exception as e:\n",
    "                print(f\"图像处理出错，将仅使用文本查询: {str(e)}\")\n",
    "                messages.append({\"role\": \"user\", \"content\": context_prompt})\n",
    "        else:\n",
    "            messages.append({\"role\": \"user\", \"content\": context_prompt})\n",
    "        \n",
    "        completion = client.chat.completions.create(\n",
    "            model=\"ernie-4.5-turbo-vl-32k\",\n",
    "            messages=messages,\n",
    "            stream=False,\n",
    "        )\n",
    "        \n",
    "        return completion.choices[0].message.content\n",
    "    except Exception as e:\n",
    "        error_message = f\"生成回答过程出错: {str(e)}\"\n",
    "        print(error_message)\n",
    "        return f\"抱歉，生成回答时出现了错误: {str(e)}。请稍后再试。\"\n",
    "\n",
    "def test_artifact_qa():\n",
    "    artifact_name = \"青铜器\"\n",
    "    \n",
    "    relations = get_related_kg_info(artifact_name, kg_data)\n",
    "    kg_info = format_kg_info(relations)\n",
    "    \n",
    "    artifact_doc = None\n",
    "    for doc in documents:\n",
    "        filename = os.path.basename(doc[\"metadata\"][\"source\"])\n",
    "        if filename.startswith(artifact_name) or artifact_name in doc[\"content\"]:\n",
    "            artifact_doc = doc[\"content\"]\n",
    "            break\n",
    "    \n",
    "    context = f\"{artifact_doc}\\n\\n{kg_info}\" if artifact_doc else kg_info\n",
    "    \n",
    "    query = f\"{artifact_name}的历史背景是什么？\"\n",
    "    \n",
    "    response = generate_response(client, query, context, None, artifact_name)\n",
    "    print(f\"问题: {query}\")\n",
    "    print(f\"回答: {response}\")\n",
    "\n",
    "test_artifact_qa()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6 基于Plotly的数据可视化\n",
    "\n",
    "使用Plotly创建中国朝代时间线，并标注特定文物对应的朝代以及使用Plotly和NetworkX可视化文物相关的知识图谱。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import plotly.express as px\n",
    "\n",
    "def get_chinese_dynasties():\n",
    "    \"\"\"获取中国历史朝代数据\"\"\"\n",
    "    dynasties = [\n",
    "        {\"name\": \"夏朝\", \"start\": -2100, \"end\": -1600, \"color\": \"#FFD700\"},\n",
    "        {\"name\": \"商朝\", \"start\": -1600, \"end\": -1046, \"color\": \"#FFA500\"},\n",
    "        {\"name\": \"周朝\", \"start\": -1046, \"end\": -256, \"color\": \"#FF8C00\"},\n",
    "        {\"name\": \"秦朝\", \"start\": -221, \"end\": -206, \"color\": \"#FF7F50\"},\n",
    "        {\"name\": \"汉朝\", \"start\": -202, \"end\": 220, \"color\": \"#FF6347\"},\n",
    "        {\"name\": \"三国\", \"start\": 220, \"end\": 280, \"color\": \"#FF4500\"},\n",
    "        {\"name\": \"晋朝\", \"start\": 266, \"end\": 420, \"color\": \"#FF0000\"},\n",
    "        {\"name\": \"南北朝\", \"start\": 420, \"end\": 589, \"color\": \"#DC143C\"},\n",
    "        {\"name\": \"隋朝\", \"start\": 581, \"end\": 618, \"color\": \"#B22222\"},\n",
    "        {\"name\": \"唐朝\", \"start\": 618, \"end\": 907, \"color\": \"#8B0000\"},\n",
    "        {\"name\": \"五代十国\", \"start\": 907, \"end\": 979, \"color\": \"#800000\"},\n",
    "        {\"name\": \"宋朝\", \"start\": 960, \"end\": 1279, \"color\": \"#8B4513\"},\n",
    "        {\"name\": \"元朝\", \"start\": 1271, \"end\": 1368, \"color\": \"#A0522D\"},\n",
    "        {\"name\": \"明朝\", \"start\": 1368, \"end\": 1644, \"color\": \"#D2691E\"},\n",
    "        {\"name\": \"清朝\", \"start\": 1644, \"end\": 1912, \"color\": \"#CD853F\"}\n",
    "    ]\n",
    "    return dynasties\n",
    "\n",
    "def get_artifact_dynasty_mapping():\n",
    "    \"\"\"获取文物与朝代的对应关系\"\"\"\n",
    "    mapping = {\n",
    "        \"青铜器\": [\"商朝\", \"周朝\"],\n",
    "        \"兵马俑\": [\"秦朝\"],\n",
    "        \"敦煌壁画\": [\"唐朝\", \"宋朝\", \"元朝\"],\n",
    "        \"唐三彩\": [\"唐朝\"],\n",
    "        \"越王勾践剑\": [\"周朝\"],\n",
    "        \"甲骨文\": [\"商朝\"],\n",
    "        \"四羊方尊\": [\"商朝\"],\n",
    "        \"汉长信宫灯\": [\"汉朝\"],\n",
    "        \"曾侯乙编钟\": [\"周朝\"],\n",
    "        \"清明上河图\": [\"宋朝\"]\n",
    "    }\n",
    "    return mapping\n",
    "\n",
    "def create_timeline_chart(artifact_name=None):\n",
    "    \"\"\"创建中国朝代时间线图表\"\"\"\n",
    "    dynasties = get_chinese_dynasties()\n",
    "    \n",
    "    timeline_data = []\n",
    "    for dynasty in dynasties:\n",
    "        duration = dynasty[\"end\"] - dynasty[\"start\"]\n",
    "        \n",
    "        opacity = 0.7\n",
    "        if artifact_name:\n",
    "            mapping = get_artifact_dynasty_mapping()\n",
    "            if artifact_name in mapping and dynasty[\"name\"] in mapping[artifact_name]:\n",
    "                opacity = 1.0  \n",
    "        \n",
    "        timeline_data.append({\n",
    "            \"朝代\": dynasty[\"name\"],\n",
    "            \"开始年份\": dynasty[\"start\"],\n",
    "            \"结束年份\": dynasty[\"end\"],\n",
    "            \"持续时间\": duration,\n",
    "            \"颜色\": dynasty[\"color\"],\n",
    "            \"透明度\": opacity\n",
    "        })\n",
    "    \n",
    "    df = pd.DataFrame(timeline_data)\n",
    "    \n",
    "    df[\"排序\"] = df[\"开始年份\"]\n",
    "    \n",
    "    df[\"显示开始\"] = df[\"开始年份\"].apply(lambda x: f\"{abs(x)}BC\" if x < 0 else f\"{x}AD\")\n",
    "    df[\"显示结束\"] = df[\"结束年份\"].apply(lambda x: f\"{abs(x)}BC\" if x < 0 else f\"{x}AD\")\n",
    "    df[\"时期\"] = df.apply(lambda x: f\"{x['显示开始']} - {x['显示结束']}\", axis=1)\n",
    "    \n",
    "    fig = px.timeline(\n",
    "        df.sort_values(\"排序\"), \n",
    "        x_start=\"开始年份\", \n",
    "        x_end=\"结束年份\", \n",
    "        y=\"朝代\",\n",
    "        color=\"朝代\",\n",
    "        hover_data=[\"时期\", \"持续时间\"],\n",
    "        color_discrete_map={row[\"朝代\"]: row[\"颜色\"] for _, row in df.iterrows()},\n",
    "        opacity=[row[\"透明度\"] for _, row in df.iterrows()]\n",
    "    )\n",
    "    \n",
    "    fig.update_layout(\n",
    "        title=f\"中国历史朝代时间线{' - ' + artifact_name if artifact_name else ''}\",\n",
    "        xaxis=dict(\n",
    "            title=\"年份\",\n",
    "            tickvals=[-2000, -1500, -1000, -500, 0, 500, 1000, 1500, 2000],\n",
    "            ticktext=[\"2000BC\", \"1500BC\", \"1000BC\", \"500BC\", \"0\", \"500AD\", \"1000AD\", \"1500AD\", \"2000AD\"],\n",
    "            showgrid=True,\n",
    "            gridcolor=\"rgba(200,200,200,0.2)\"\n",
    "        ),\n",
    "        yaxis=dict(\n",
    "            title=\"\",\n",
    "            autorange=\"reversed\"\n",
    "        ),\n",
    "        height=400,\n",
    "        plot_bgcolor=\"white\",\n",
    "        paper_bgcolor=\"white\",\n",
    "        margin=dict(l=50, r=50, t=50, b=50),\n",
    "        showlegend=False\n",
    "    )\n",
    "    \n",
    "    if artifact_name:\n",
    "        mapping = get_artifact_dynasty_mapping()\n",
    "        if artifact_name in mapping:\n",
    "            for dynasty_name in mapping[artifact_name]:\n",
    "                dynasty = next((d for d in dynasties if d[\"name\"] == dynasty_name), None)\n",
    "                if dynasty:\n",
    "                    mid_year = (dynasty[\"start\"] + dynasty[\"end\"]) / 2\n",
    "                    fig.add_annotation(\n",
    "                        x=mid_year,\n",
    "                        y=dynasty_name,\n",
    "                        text=f\"《{artifact_name}》\",\n",
    "                        showarrow=True,\n",
    "                        arrowhead=2,\n",
    "                        arrowcolor=\"#8C3D2B\",\n",
    "                        arrowsize=1,\n",
    "                        arrowwidth=2,\n",
    "                        bgcolor=\"rgba(255,255,255,0.9)\",\n",
    "                        bordercolor=\"#8C3D2B\",\n",
    "                        borderwidth=1,\n",
    "                        borderpad=4,\n",
    "                        font=dict(color=\"#8C3D2B\", size=12)\n",
    "                    )\n",
    "    \n",
    "    return fig\n",
    "\n",
    "timeline_fig = create_timeline_chart(\"青铜器\")\n",
    "timeline_fig.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import networkx as nx\n",
    "import plotly.graph_objects as go\n",
    "\n",
    "def visualize_artifact_kg(artifact_name, kg_data):\n",
    "    if not kg_data:\n",
    "        return None\n",
    "    \n",
    "    try:\n",
    "        G = nx.DiGraph()\n",
    "        \n",
    "        artifact_node = None\n",
    "        for node in kg_data['nodes']:\n",
    "            if node['name'] == artifact_name and node['type'] == 'artifact':\n",
    "                artifact_node = node\n",
    "                break\n",
    "        \n",
    "        if not artifact_node:\n",
    "            return None\n",
    "        \n",
    "        G.add_node(\n",
    "            artifact_node['id'],\n",
    "            name=artifact_node['name'],\n",
    "            type=artifact_node['type'],\n",
    "            properties=artifact_node.get('properties', {})\n",
    "        )\n",
    "        \n",
    "        related_nodes = set()\n",
    "        for edge in kg_data['edges']:\n",
    "            if edge['source'] == artifact_node['id']:\n",
    "                related_nodes.add(edge['target'])\n",
    "                G.add_edge(\n",
    "                    edge['source'],\n",
    "                    edge['target'],\n",
    "                    type=edge['type'],\n",
    "                    properties=edge.get('properties', {})\n",
    "                )\n",
    "            elif edge['target'] == artifact_node['id']:\n",
    "                related_nodes.add(edge['source'])\n",
    "                G.add_edge(\n",
    "                    edge['source'],\n",
    "                    edge['target'],\n",
    "                    type=edge['type'],\n",
    "                    properties=edge.get('properties', {})\n",
    "                )\n",
    "        \n",
    "        for node in kg_data['nodes']:\n",
    "            if node['id'] in related_nodes:\n",
    "                G.add_node(\n",
    "                    node['id'],\n",
    "                    name=node['name'],\n",
    "                    type=node['type'],\n",
    "                    properties=node.get('properties', {})\n",
    "                )\n",
    "        \n",
    "        node_types = {\n",
    "            'artifact': {'color': '#8C3D2B', 'label': '文物'},\n",
    "            'person': {'color': '#308C6C', 'label': '人物'},\n",
    "            'era': {'color': '#D4A76A', 'label': '时代'},\n",
    "            'collection': {'color': '#BC6C5A', 'label': '收藏地'},\n",
    "            'category': {'color': '#9E6B52', 'label': '类别'}\n",
    "        }\n",
    "        \n",
    "        pos = nx.spring_layout(G, seed=42, k=0.3)\n",
    "        \n",
    "        node_x = []\n",
    "        node_y = []\n",
    "        node_text = []\n",
    "        node_color = []\n",
    "        node_size = []\n",
    "        node_symbols = []\n",
    "        \n",
    "        for node_id, coords in pos.items():\n",
    "            node_x.append(coords[0])\n",
    "            node_y.append(coords[1])\n",
    "            \n",
    "            node_data = G.nodes[node_id]\n",
    "            node_name = node_data['name']\n",
    "            node_type = node_data.get('type', 'default')\n",
    "            \n",
    "            node_text.append(f\"{node_name} ({node_types.get(node_type, {'label': '其他'})['label']})\")\n",
    "            node_color.append(node_types.get(node_type, {'color': '#AAAAAA'})['color'])\n",
    "            \n",
    "            if node_id == artifact_node['id']:\n",
    "                node_size.append(30)\n",
    "                node_symbols.append('star')\n",
    "            else:\n",
    "                node_size.append(20)\n",
    "                symbol_map = {'person': 'circle', 'era': 'square', 'collection': 'diamond', 'category': 'cross'}\n",
    "                node_symbols.append(symbol_map.get(node_type, 'circle'))\n",
    "        \n",
    "        edge_x = []\n",
    "        edge_y = []\n",
    "        edge_text = []\n",
    "        \n",
    "        relation_name_map = {\n",
    "            \"created_in\": \"创作于\",\n",
    "            \"belongs_to\": \"属于\",\n",
    "            \"houses\": \"收藏\",\n",
    "            \"created_by\": \"创作者\",\n",
    "            \"associated_with\": \"相关人物\",\n",
    "            \"found_in\": \"发现于\",\n",
    "            \"owned_by\": \"拥有者\",\n",
    "            \"collected_by\": \"收藏于\",\n",
    "            \"flourished_in\": \"繁荣于\",\n",
    "            \"lived_in\": \"生活于\"\n",
    "        }\n",
    "        \n",
    "        for edge in G.edges(data=True):\n",
    "            x0, y0 = pos[edge[0]]\n",
    "            x1, y1 = pos[edge[1]]\n",
    "            \n",
    "            edge_x.append(x0)\n",
    "            edge_x.append((x0 + x1) / 2 + (y1 - y0) * 0.1)\n",
    "            edge_x.append(x1)\n",
    "            edge_x.append(None)\n",
    "            \n",
    "            edge_y.append(y0)\n",
    "            edge_y.append((y0 + y1) / 2 + (x0 - x1) * 0.1) \n",
    "            edge_y.append(y1)\n",
    "            edge_y.append(None)\n",
    "            \n",
    "            relation_type = edge[2]['type']\n",
    "            relation_label = relation_name_map.get(relation_type, relation_type)\n",
    "            edge_text.append(relation_label)\n",
    "        \n",
    "        edge_trace = go.Scatter(\n",
    "            x=edge_x, y=edge_y,\n",
    "            line=dict(width=1, color='rgba(140, 61, 43, 0.5)'),\n",
    "            hoverinfo='none',\n",
    "            mode='lines',\n",
    "            showlegend=False\n",
    "        )\n",
    "        \n",
    "        node_trace = go.Scatter(\n",
    "            x=node_x, y=node_y,\n",
    "            mode='markers+text',\n",
    "            text=node_text,\n",
    "            textposition=\"bottom center\",\n",
    "            marker=dict(\n",
    "                showscale=False,\n",
    "                color=node_color,\n",
    "                size=node_size,\n",
    "                symbol=node_symbols,\n",
    "                line=dict(width=1, color='white')\n",
    "            ),\n",
    "            hoverinfo='text'\n",
    "        )\n",
    "        \n",
    "        annotations = []\n",
    "        for i, (x0, y0, x1, y1) in enumerate(zip(\n",
    "            [pos[edge[0]][0] for edge in G.edges()],\n",
    "            [pos[edge[0]][1] for edge in G.edges()],\n",
    "            [pos[edge[1]][0] for edge in G.edges()],\n",
    "            [pos[edge[1]][1] for edge in G.edges()]\n",
    "        )):\n",
    "            if i < len(edge_text):\n",
    "                annotations.append(dict(\n",
    "                    x=(x0 + x1) / 2,\n",
    "                    y=(y0 + y1) / 2,\n",
    "                    xref=\"x\", yref=\"y\",\n",
    "                    text=edge_text[i],\n",
    "                    showarrow=False,\n",
    "                    font=dict(size=10, color=\"#333\"),\n",
    "                    bgcolor=\"rgba(255,255,255,0.8)\",\n",
    "                    borderpad=2,\n",
    "                    bordercolor=\"#E0D1B9\",\n",
    "                    borderwidth=1\n",
    "                ))\n",
    "        \n",
    "        fig = go.Figure(data=[edge_trace, node_trace],\n",
    "                      layout=go.Layout(\n",
    "                          title=f\"《{artifact_name}》相关知识图谱\",\n",
    "                          showlegend=False,\n",
    "                          annotations=annotations,\n",
    "                          hovermode='closest',\n",
    "                          margin=dict(b=20, l=5, r=5, t=60),\n",
    "                          xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),\n",
    "                          yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),\n",
    "                          plot_bgcolor='white',\n",
    "                          paper_bgcolor='white'\n",
    "                      ))\n",
    "        \n",
    "        legend_items = []\n",
    "        for type_name, type_info in node_types.items():\n",
    "            legend_items.append(\n",
    "                go.Scatter(\n",
    "                    x=[None], y=[None],\n",
    "                    mode='markers',\n",
    "                    marker=dict(size=10, color=type_info['color']),\n",
    "                    name=type_info['label'],\n",
    "                    showlegend=True\n",
    "                )\n",
    "            )\n",
    "        \n",
    "        for trace in legend_items:\n",
    "            fig.add_trace(trace)\n",
    "        \n",
    "        fig.update_layout(\n",
    "            legend=dict(\n",
    "                orientation=\"h\",\n",
    "                yanchor=\"bottom\",\n",
    "                y=-0.1,\n",
    "                xanchor=\"center\",\n",
    "                x=0.5\n",
    "            )\n",
    "        )\n",
    "        \n",
    "        return fig\n",
    "    except Exception as e:\n",
    "        print(f\"知识图谱可视化过程出错: {str(e)}\")\n",
    "        return None\n",
    "\n",
    "kg_fig = visualize_artifact_kg(\"青铜器\", kg_data)\n",
    "kg_fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ai-studio-static-online.cdn.bcebos.com/4963bc7292304718812ccd9f09fdd20c9672078a9b564d3baae800cbca1bb5f1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "智慧博物馆文物讲解助手DEMO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📚 正在加载 '青铜器' 的相关信息...\n",
      "\n",
      "🔍 生成文物介绍...\n"
     ]
    },
    {
     "ename": "NameError",
     "evalue": "name 'generate_response' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mNameError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[15]\u001b[39m\u001b[32m, line 53\u001b[39m\n\u001b[32m     50\u001b[39m             \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33m⚠️ ERNIE客户端未初始化，无法生成回答\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m     52\u001b[39m \u001b[38;5;66;03m# 运行演示\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m53\u001b[39m \u001b[43mmuseum_assistant_demo\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m青铜器\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[15]\u001b[39m\u001b[32m, line 21\u001b[39m, in \u001b[36mmuseum_assistant_demo\u001b[39m\u001b[34m(artifact_name)\u001b[39m\n\u001b[32m     18\u001b[39m intro_query = \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m请全面介绍\u001b[39m\u001b[38;5;132;01m{\u001b[39;00martifact_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m的历史背景、艺术特点、文化价值和重要意义。\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m     20\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m client:\n\u001b[32m---> \u001b[39m\u001b[32m21\u001b[39m     intro_response = \u001b[43mgenerate_response\u001b[49m(client, intro_query, context, \u001b[38;5;28;01mNone\u001b[39;00m, artifact_name)\n\u001b[32m     22\u001b[39m     \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m\"\u001b[39m + intro_response)\n\u001b[32m     23\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n",
      "\u001b[31mNameError\u001b[39m: name 'generate_response' is not defined"
     ]
    }
   ],
   "source": [
    "def museum_assistant_demo(artifact_name=\"青铜器\"):\n",
    "    \"\"\"演示智慧博物馆文物讲解助手的核心功能\"\"\"\n",
    "    print(f\"📚 正在加载 '{artifact_name}' 的相关信息...\")\n",
    "    \n",
    "    relations = get_related_kg_info(artifact_name, kg_data)\n",
    "    kg_info = format_kg_info(relations)\n",
    "    \n",
    "    artifact_doc = None\n",
    "    for doc in documents:\n",
    "        filename = os.path.basename(doc[\"metadata\"][\"source\"])\n",
    "        if filename.startswith(artifact_name) or artifact_name in doc[\"content\"]:\n",
    "            artifact_doc = doc[\"content\"]\n",
    "            break\n",
    "    \n",
    "    context = f\"{artifact_doc}\\n\\n{kg_info}\" if artifact_doc else kg_info\n",
    "    \n",
    "    print(\"\\n🔍 生成文物介绍...\")\n",
    "    intro_query = f\"请全面介绍{artifact_name}的历史背景、艺术特点、文化价值和重要意义。\"\n",
    "    \n",
    "    if client:\n",
    "        intro_response = generate_response(client, intro_query, context, None, artifact_name)\n",
    "        print(\"\\n\" + intro_response)\n",
    "    else:\n",
    "        print(\"\\n⚠️ ERNIE客户端未初始化，无法生成文物介绍\")\n",
    "    \n",
    "    print(\"\\n📊 创建知识图谱可视化...\")\n",
    "    kg_fig = visualize_artifact_kg(artifact_name, kg_data)\n",
    "    if kg_fig:\n",
    "        print(\"知识图谱可视化创建成功\")\n",
    "    else:\n",
    "        print(\"无法创建知识图谱可视化\")\n",
    "    \n",
    "    print(\"\\n📅 创建历史时间线...\")\n",
    "    timeline_fig = create_timeline_chart(artifact_name)\n",
    "    print(\"历史时间线创建成功\")\n",
    "    \n",
    "    print(\"\\n💬 演示文物问答...\")\n",
    "    demo_questions = [\n",
    "        f\"{artifact_name}的历史背景是什么？\",\n",
    "        f\"{artifact_name}有哪些艺术特点？\",\n",
    "        f\"{artifact_name}的文化价值是什么？\"\n",
    "    ]\n",
    "    \n",
    "    for question in demo_questions[:1]: \n",
    "        print(f\"\\n问题: {question}\")\n",
    "        if client:\n",
    "            answer = generate_response(client, question, context, None, artifact_name)\n",
    "            print(f\"回答: {answer}\")\n",
    "        else:\n",
    "            print(\"⚠️ ERNIE客户端未初始化，无法生成回答\")\n",
    "\n",
    "# 运行演示\n",
    "museum_assistant_demo(\"青铜器\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8 总结与未来展望\n",
    "\n",
    "在本教程中，我们构建了一个完整的智慧博物馆文物讲解助手，它结合了多项技术：\n",
    "\n",
    "* 文物知识库：提供权威、详细的文物背景知识\n",
    "* 词嵌入和向量检索：实现文本的语义搜索功能\n",
    "* 知识图谱：构建文物与人物、时代、场所等的关系网络\n",
    "* ERNIE 4.5 Turbo VL：多模态大模型能力，实现图像识别和生成讲解\n",
    "* 检索增强生成（RAG）：结合向量检索和知识图谱，提供高质量回答\n",
    "* 可视化组件：时间线和知识图谱可视化，增强用户理解\n",
    "\n",
    "在未来的设计中增强多模态交互能力包括添加语音交互功能，支持语音问答，使体验更自然，实现3D文物模型展示，允许用户从多角度观察文物细节，通过AR/VR技术创建沉浸式展览体验等，通过上述方向的拓展，智慧博物馆文物讲解助手可以发展成为连接传统文化与现代技术的重要桥梁，不仅服务于普通大众的文化教育需求，也能为专业研究和文化传承提供有力支持。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "请点击[此处](https://ai.baidu.com/docs#/AIStudio_Project_Notebook/a38e5576)查看本环境基本用法.  <br>\n",
    "Please click [here ](https://ai.baidu.com/docs#/AIStudio_Project_Notebook/a38e5576) for more detailed instructions. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
